/* disp.c : dispatcher
 */

#include <xeroskernel.h>
#include <xeroslib.h>
#include <stdarg.h>

void addtoblockedQ(pcb *p);

static pcb      *head = NULL;
static pcb      *tail = NULL;
// static pcb 		*blockedQ = NULL;

void     dispatch( void ) {
/********************************/

    pcb         *p,*blocked_p;
    int         r, buffer_len, device_no, fd;
    funcptr     fp;
    funcptr		*fpp;
    int         stack, igen1, igen2;
    va_list     ap;
	char		*str;
    unsigned int ui, dest_pid, *from_pid;
	void		*buffer, *args;
	unsigned long command;

    for( p = next(); p; ) {
      //      kprintf("Process %x selected stck %x\n", p, p->esp);
		
		r = contextswitch( p );
		switch( r ) {
		case( SYS_CREATE ):
			ap = (va_list)p->args;
			fp = (funcptr)(va_arg( ap, int ) );
			stack = va_arg( ap, int );
			p->ret = create( fp, stack );
			break;
		case( SYS_YIELD ):
            ready( p );
			p = next();
			break;
		case( SYS_STOP ):
			p->state = STATE_STOPPED;
			proc_cleanup(p); 
			p = next();
			break;
		case( SYS_GET_PID ):
			p->ret = p->pid;
			break;
		case( SYS_PUT):
			ap = (va_list)p->args;
			str = va_arg(ap,char*);
			kprintf("%s", str);
			break;
		case( SYS_SEND ):
			ap = (va_list)p->args;
			dest_pid = (unsigned int)(va_arg(ap, unsigned int));
			buffer = (void *)(va_arg(ap, void*));
			buffer_len = (int)(va_arg(ap, int));
			p->ret = send(dest_pid, buffer, buffer_len,p);
			p = next();
			break;
		case( SYS_RECV ):
			ap = (va_list)p->args;
			from_pid = (unsigned int*)(va_arg(ap, unsigned int*));
			buffer = (void *)(va_arg(ap, void*));
			buffer_len = (int)(va_arg(ap, int));
			p->ret = recv(from_pid, buffer, buffer_len,p);
			p=next();
			break;
        case( SYS_SLEEP ):
            ap = (va_list)p->args;
            ui = va_arg(ap,unsigned int);
            sleep(p,ui);
            p = next();
            break;
        case ( SYS_TIMER ):
            if(p->pid != idleproc->pid) ready(p);
            p = next();
            tick();
            end_of_intr();
            break;
        case ( SYS_SIGHANDLER ):
        	ap = (va_list)p->args;
            igen1 = va_arg(ap,int);
            fp = (funcptr)va_arg(ap,void*);
            fpp = (funcptr)va_arg(ap,void*);
            p->ret = installHandler(p, igen1, fp, fpp);
         	break;   
        case ( SYS_SIGNAL ):
        	ap = (va_list)p->args;
            igen1 = va_arg(ap,int);
            igen2 = va_arg(ap,int);
            p->ret = signal(igen1,igen2);
        	break;
        case ( SYS_SIGWAIT ):
        	p->state = STATE_BLOCKED;
        	p = next();
        	break;
        case ( SYS_SIGRETURN ):
		    //set p->esp to old stack
        	ap = (va_list)p->args;
		    p->esp = va_arg(ap,void*);
		    
		    //remove signal from pendingmask
		    igen1 = findLargestBit(p->pendingmask);
		    p->pendingmask = p->pendingmask ^ (1 << igen1);
		    //remove sig from acceptedmask
		    p->acceptedmask = p->acceptedmask ^ (1 << igen1);

		    //signal if exist higher priority signal that wasn't interrupted
	    	if(p->acceptedmask > 0 && isLargestBit) {
	    		igen1 = findLargestBit(p->acceptedmask);
	    		signal(p->pid,igen1);
	    	}
        	break;
		case( SYS_OPEN ):
			ap = (va_list)p->args;
			device_no = va_arg(ap,int);
			p->ret = di_open(p,device_no);
			ready(p);
			p = next();
			break;
		case( SYS_CLOSE ):
			ap = (va_list)p->args;
			fd = va_arg(ap,int);
			p->ret = di_close(p,fd);
			ready(p);
			p = next();
			break;
		case( SYS_WRITE ):
			ap = (va_list)p->args;
			fd = va_arg(ap,int);
			buffer = (void *)(va_arg(ap, void*));
			buffer_len = (int)(va_arg(ap, int));
			p->state = STATE_BLOCKED;
			p->ret = di_write(p, fd, buffer, buffer_len);
			ready(p);
			p = next();
			break;
		case( SYS_READ ):
			ap = (va_list)p->args;
			fd = va_arg(ap,int);
			buffer = (void *)(va_arg(ap, void*));
			buffer_len = (int)(va_arg(ap, int));
			p->ret = di_read(p, fd, buffer, buffer_len);
			if(p->ret >= 0 )
				ready(p);
			else{
				p->state = STATE_BLOCKED;
				blocked_p = p;
			}
			p = next();
			break;
		case( SYS_IOCTL ):
			ap = (va_list)p->args;
			fd = va_arg(ap,int);
			command = va_arg(ap, unsigned long);
			args = (void *)(va_arg(ap, void*));
			p->ret = di_ioctl(p, fd, command, args);
			ready(p);
			p = next();
			break;
		case ( SYS_KEYBOARD ):
			//kprintf("interrupt!!\n");
			fillbuffer();
			if(blocked_p != NULL){
				blocked_p->ret = kbdflush();
				if(blocked_p->ret >= 0){
					ready(blocked_p);
					blocked_p = NULL;
				}
			}
			end_of_intr();
			break;
		default:
   	     kprintf( "Bad Sys request %d, pid = %d\n", r, p->pid );
		}
    }

    kprintf( "Out of processes: dying\n" );
    
    for( ;; );
}

extern void dispatchinit( void ) {
/********************************/

 	//bzero( proctab, sizeof( pcb ) * MAX_PROC );
 	memset(proctab, 0, sizeof( pcb ) * MAX_PROC);
}

extern void     ready( pcb *p ) {
/*******************************/

    p->next = NULL;
    p->state = STATE_READY;

    if( tail ) {
        tail->next = p;
    } else {
        head = p;
    }

    tail = p;
}

extern void proc_cleanup(pcb *p){
	int i;
	pcb *q;
	
	//notify senders
	q = p->senderQ;
	while(q != NULL){
		ready(q);
		q->ret = -1;
		q = q->next;
	}
	
	//notify receivers
	for(i=0; i<MAX_PROC; i++){
		q = &proctab[i];
		if(q->state==STATE_RECV && *q->from_pid==p->pid){
			ready(q);
			q->ret = -1;
		}
	}
	
	//kfree
	kfree((void *)p->free_addr);
}

extern pcb      *next( void ) {
/*****************************/
	int i;
    pcb *p;

    if(head == NULL && tail == NULL) {
    	return idleproc;
    }

    p = head;

    if( p ) {
        head = p->next;
        if( !head ) {
            tail = NULL;
        }
    }

    return( p );
}

/*
	Gets the PCB table index with ID
*/
int  getPCBi(unsigned int id) {
	int i;
	for(i = 0; i < MAX_PROC; i++) {
		if(proctab[i].pid == id) {
			return i;
		}
	}
	return -1;
}

/*
	Finds the most significant bit position
*/
int findLargestBit(unsigned int bits ) {	
	int sig = -1;
	int pend = bits;
	if(pend == 0) return -1;
	while( pend > 0 ) {
		pend = pend >> 1;
		sig++;
	}

	return sig;
}

/*
	Checks if a larger unsignalled bit needs to be signalled
*/
Bool isLargestBit(pcb *p) {
	int accepted, pending;
	accepted = findLargestBit(p->acceptedmask);
	pending = findLargestBit(p->pendingmask);
	if(accepted > pending) {
		return 1;
	} else {
		return 0;
	}
}
